package com.siteseat.common.spring;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;

import com.siteseat.common.exception.InternalException;

/**
 *
 */
public class ExposedBeanSet {
  
  private Map<String,ExposedBean> map = new HashMap<String,ExposedBean>();
  
  private Set<ExposedBean> set = new HashSet<ExposedBean>();
  
  /**
   * 
   */
  public void initializeAll() {
    for( ExposedBean b: set ) {
      b.get();
    }
  }
  
  /**
   * 
   */
  public void add( ConfigurableApplicationContext owner, List<ExposedBean> beansToExpose ) {
    String moduleId = owner.getId();
    
    for( ExposedBean b: beansToExpose ) {
      for( String n: b.getExposedNames() ) {
        ExposedBean x = map.get( n );
        if( x != null && x != b ) {
          throw new InternalException( "Duplicated bean name <" + n + "> to expose from sub module: " + moduleId );
        }
        map.put( n, b );
      }
      
      b.setContext( owner );
      set.add( b );
    }
  }
    
  /**
   * 
   *
   * @return
   */
  public boolean isNotEmpty() {
    return !this.set.isEmpty();
  }
  
  
  /**
   * 
   *
   * @param name
   * @return
   */
  public boolean contains( String name ) {
    return map.containsKey( name );
  }
  
  /**
   * 
   *
   * @param name
   * @return
   */
  public boolean containsDefinition( String name ) {    
    return map.containsKey( name );
  }
  

  /**
   *
   * @param <A>
   * @param beanName
   * @param annotationType
   * @return
   */
  public <A extends Annotation> A findAnnotation( String beanName, Class<A> annotationType ) {    
    ExposedBean d = map.get( beanName );
    if( d == null ) {
      return null;
    }    
    return d.findAnnotationOnBean( annotationType );
  }
  
  /**
   *
   * @param name
   * @return
   */
  public String[] getAliases( String name ) {
    ExposedBean d = map.get( name );
    if( d == null ) {
      return null;
    }
    return d.getAliases();
  }
  
  /**
   *
   * @param <T>
   * @param requiredType
   * @return
   * @throws BeansException
   */
  public <T> T get( Class<T> requiredType ) {
    for( ExposedBean d: set ) {
      T r = d.get( requiredType );
      if( r != null ) {
        return r;
      }
    }
    return null;
  }
  
  /**
   * 
   *
   * @param <T>
   * @param name
   * @param requiredType
   * @return
   * @throws BeansException
   */
  public <T> T getBean( String name, Class<T> requiredType ) {
    ExposedBean d = map.get( name );
    if( d == null ) {
      return null;
    }    
    return d.get( requiredType );
  }
  

  /**
   * 
   *
   * @param name
   * @param args
   * @return
   * @throws BeansException
   */
  public Object getBean( String name, Object... args ) {    
    ExposedBean d = map.get( name );
    if( d == null ) {
      return null;
    }    
    return d.get( args );
  }
 
  /**
   * 
   *
   * @param name
   * @return
   * @throws BeansException
   */
  public Object getBean( String name ) throws BeansException {    
    ExposedBean d = map.get( name );
    if( d == null ) {
      return null;
    }
    return d.get();
  }
  
  /**
   * 
   *
   * @return
   */
  public int getBeanDefinitionCount() {
    return set.size();
  }
  
  /**
   * 
   *
   * @param beanName
   * @return
   * @throws NoSuchBeanDefinitionException
   */
  public BeanDefinition getBeanDefinition( String beanName ) {
    ExposedBean d = map.get( beanName );
    if( d == null ) {
      return null;
    }
    return d.getBeanDefinition();
  }
  
  /**
   * 
   *
   * @return
   */
  public Collection<String> getBeanDefinitionNames() {
    return map.keySet();
  }
  
  /**
   * 
   *
   * @param type
   * @param includeNonSingletons
   * @param allowEagerInit
   * @return
   */
  @SuppressWarnings( "unchecked" )
  public List<String> getBeanNamesForType( Class type, boolean includeNonSingletons, boolean allowEagerInit ) {
    List<String> r = new ArrayList<String>( set.size() );
    
    for( ExposedBean d: set ) {
      if( d.isForType( type, includeNonSingletons, allowEagerInit ) ) {
        r.addAll( d.getExposedNames() );
      }
    }

    return r;
  }
  
  /**
   * 
   *
   * @param <T>
   * @param type
   * @param includeNonSingletons
   * @param allowEagerInit
   * @return
   */
  @SuppressWarnings( "unchecked" )
  public <T> Map<String, T> getBeansOfType( Class<T> type, boolean includeNonSingletons, boolean allowEagerInit ) {
    Map<String, T> r = new HashMap<String, T>( set.size() );
    
    for( ExposedBean d: set ) {
      if( d.isForType( type, includeNonSingletons, allowEagerInit ) ) {
        T b = (T)d.get();
        for( String n: d.getExposedNames() ) {
          r.put( n, b );
        }
      }
    }

    return r;
  }
  
  /**
   * 
   *
   * @param type
   * @return
   */
  @SuppressWarnings( { "cast", "unchecked" } )
  public List<String> getBeanNamesForType( Class type ) {
    List<String> r = new ArrayList<String>( set.size() );
    
    for( ExposedBean d: set ) {
      Object b = (Object)d.get( type );
      if( b == null ) {
        continue;
      }
      r.addAll( d.getExposedNames() );
    }
    return r;
  }
  
  /**
   * 
   *
   * @param <T>
   * @param type
   * @return
   * @throws BeansException
   */
  public <T> Map<String, T> getBeansOfType( Class<T> type ) {
    Map<String,T> r = new HashMap<String,T>( set.size() );
    
    for( ExposedBean d: set ) {
      T b = d.get( type );
      if( b == null ) {
        continue;
      }
      for( String n: d.getExposedNames() ) {
        r.put( n, b );
      }
    }
    return r;
  }
  
  /**
   * 
   *
   * @param annotationType
   * @return
   * @throws BeansException
   */
  public Map<String, Object> getBeansWithAnnotation( Class<? extends Annotation> annotationType ) {
    Map<String,Object> r = new HashMap<String,Object>( set.size() );
    
    for( ExposedBean d: set ) {
      Object b = d.findAnnotationOnBean( annotationType );
      if( b == null ) {
        continue;
      }
      for( String n: d.getExposedNames() ) {
        r.put( n, b );
      }
    }
    return r;
  }
  
  /**
   * 
   *
   * @param name
   * @return
   */
  public Class<?> getType( String name ) {
    ExposedBean d = map.get( name );
    if( d == null ) {
      return null;
    }    
    return d.getFactory().getType( d.getInnerName() );
  }

  public ExposedBean findDescription( String name ) {
    return map.get( name );
  }
  
  public Object getSingleton( String name ) {
    ExposedBean d = map.get( name );
    if( d == null ) {
      return null;
    }  
    return d.getSingleton();
  }
  
  /**
   * 
   *
   * @param beanName
   * @return
   */
  public BeanDefinition getMergedBeanDefinition( String beanName ) {
    ExposedBean d = map.get( beanName );
    if( d == null ) {
      return null;
    }  
    return d.getMergedBeanDefinition();
  }

  /**
   * 
   *
   * @return
   */
  public int getSingletonCount() {
    int r = 0;
    
    for( ExposedBean d: set ) {
      if( d.isSingleton() ) {
        r++;
      }
    }
    return r;
  }
  
  /**
   * 
   *
   * @return
   */
  public List<String> getSingletonNames() {
    List<String> r = new ArrayList<String>( set.size() );
    
    for( ExposedBean d: set ) {
      if( d.isSingleton() ) {
        r.addAll( d.getExposedNames() );
      }
    }
    return r;
  }
  
}
